package org.summerframework.component.security.spring.config;

import org.summerframework.component.security.spring.OAuthProperties;
import org.summerframework.component.security.spring.annotation.ScopeRequired;
import org.summerframework.core.util.AnnotationUtils;
import org.summerframework.core.util.HandlerMethodUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler;
import org.springframework.security.oauth2.provider.vote.ScopeVoter;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author 石超
 * @version v1.0.0
 */
@Configuration
@EnableResourceServer
@ConditionalOnClass(BearerTokenAuthenticationToken.class)
@EnableConfigurationProperties(OAuthProperties.class)
@ConditionalOnProperty(name = "spring.oauth2.ant-patterns")
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter implements Ordered {

    @Resource
    private OAuthProperties oAuthProperties;

    @Resource
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    }

    /**
     * 这里设置需要token验证的url
     * 这些url可以在WebSecurityConfigurerAdapter中排除掉，
     * 对于相同的url，如果二者都配置了验证
     * 则优先进入ResourceServerConfigurerAdapter,进行token验证。而不会进行
     * WebSecurityConfigurerAdapter 的 basic auth或表单认证。
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        RequestMatcher requestMatcher = request -> {
            HandlerExecutionChain handler = HandlerMethodUtil.getHandlerMethod(requestMappingHandlerMapping, request);

            if (handler != null && handler.getHandler() instanceof HandlerMethod) {
                return AnnotationUtils.hasAnnotation((HandlerMethod) handler.getHandler(), Arrays.asList(ScopeRequired.class));
            }

            return false;
        };

        http.requestMatchers().requestMatchers(requestMatcher).antMatchers(oAuthProperties.getAntPatterns())
                .and()
                .authorizeRequests().anyRequest().authenticated().accessDecisionManager(accessDecisionManager()).and().apply(new RequestContextConfigurer<>());
    }

    @Bean
    public AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<?>> accessDecisionVoters = new ArrayList<>();

        accessDecisionVoters.add(new ScopeVoter());
        WebExpressionVoter e = new WebExpressionVoter();
        e.setExpressionHandler(new OAuth2WebSecurityExpressionHandler());
        accessDecisionVoters.add(e);

        return new UnanimousBased(accessDecisionVoters);
    }

    @Override
    public int getOrder() {
        return 50;
    }
}
